@import org.silkframework.rule.Operator
@import org.silkframework.rule.input.TransformInput
@import org.silkframework.rule.input.PathInput
@import org.silkframework.rule.similarity.Aggregation
@import org.silkframework.rule.similarity.Comparison
@import org.silkframework.rule.RuleTraverser
@import org.silkframework.runtime.plugin.PluginDescription
@import views.editor.GenerateId
@import org.silkframework.workspace.Project

@(rule: org.silkframework.rule.Operator,
  project: Project)

@drawLevel(Seq(rule), (countLevels(rule) - 1) * 250, maxWidth(rule) * 200)

@connectOperators(rule)

@drawLevel(ops: Seq[Operator], x: Int, height: Int) = {
  @if(!ops.isEmpty) {
    @for((op, index) <- ops.zipWithIndex) {
      @drawOperator(op, x + 20, height * (index + 1) / (ops.size + 1) - 80)
    }
    @drawLevel(ops.flatMap(getChildren), x - 250, height)
  }
}

@countLevels(op: Operator) = @{
  def count(op: Operator): Int = {
    val children = getChildren(op)
    if(children.isEmpty)
      1
    else
      1 + children.map(count).max
  }
  count(op)
}

@maxWidth(op: Operator) = @{
  def findMax(ops: Seq[Operator]): Int = {
    val children = ops.flatMap(getChildren)
    if(children.isEmpty)
      ops.size
    else
      Seq(ops.size, findMax(children)).max
  }
  findMax(Seq(op))
}

@getChildren(op: Operator) = @{
  op match {
    case agg: Aggregation => agg.operators
    case cmp: Comparison => cmp.inputs.toSeq
    case transform: TransformInput => transform.inputs
    case path: PathInput => Seq.empty
  }
}

@drawOperator(op: Operator, x: Int, y: Int) = {
  @op match {
    case Aggregation(id, required, weight, aggregator, operators) => {
      @aggregationBox(id, required, weight, aggregator.plugin, parameterValues(aggregator, aggregator.plugin), x, y, true, project)
    }
    case Comparison(id, required, weight, threshold, indexing, metric, inputs) => {
      @comparisonBox(id, required, weight, threshold, metric.plugin, parameterValues(metric, metric.plugin), x, y, true, project)
    }
    case TransformInput(id, transformer, inputs) => {
      @transformationBox(id, transformer.plugin, parameterValues(transformer, transformer.plugin), x, y, true, project)
    }
    case PathInput(id, path) => {
      @pathBox(id, isSourceInput(op), path.serialize(project.config.prefixes), x, y, true, project)
    }
  }
}

@parameterValues(plugin: AnyRef, pluginType: PluginDescription[_]) = @{
  for(p <- pluginType.parameters) yield p.stringValue(plugin)
}

@**
 * Determines if an operator is a source or target input.
 *@
@isSourceInput(op: Operator) = @{
  var isSource = true
  for {
    // Get a traversable node for the given operator
    inputNode <- RuleTraverser(rule).iterateAllChildren.find(_.operator.id == op.id)
    // Find the root input, i.e., the one directly below a comparison
    rootInputNode <- inputNode.iterateParents.find(_.moveUp.exists(_.operator.isInstanceOf[Comparison]))
    // Get the comparison node
    comparisonNode <- rootInputNode.moveUp
  } {
    // The operator is a source input if it is the first child of the comparison
    isSource = comparisonNode.iterateChildren.next.operator.id == rootInputNode.operator.id
  }
  isSource
}

@connectOperators(op: Operator) = {
  <script type="text/javascript">
    // Initialization
    jsPlumb.bind("ready", function() {
      initEditor();

      var dropArea = $('#droppable');
      jsPlumb.draggable(dropArea.find('.dragDiv'));
      //jsPlumb.draggable(dropArea.find('.dragDiv'), {
      //  containment: "parent"
      //}); //TODO move

      jsPlumb.setSuspendEvents(true);
      jsPlumb.setSuspendDrawing(true);

      @connectOperator(op)

      jsPlumb.setSuspendDrawing(false, true);
      jsPlumb.setSuspendEvents(false);

      saveInstance();
    });

  </script>
}

@connectOperator(op: Operator) = {
  @op match {
    case Aggregation(id, required, weight, aggregator, operators) => {
      @* Handle children *@
      @for(op <- operators) {
        @connectOperator(op)
      }

      @* Create endpoints *@
      var endpoint_@{id}_target = jsPlumb.addEndpoint('@GenerateId(id,true)', endpointSimilarityTarget);
      var endpoint_@{id}_source = jsPlumb.addEndpoint('@GenerateId(id,true)', endpointSimilaritySource);

      @* Connect children *@
      @for(op <- operators) {
        jsPlumb.connect({
        source: endpoint_@{op.id}_source,
        target: endpoint_@{id}_target
        });
      }
    }
    case Comparison(id, required, weight, threshold, indexing, metric, inputs) => {
      @* Handle children *@
      @connectOperator(inputs.source)
      @connectOperator(inputs.target)

      @* Create endpoints *@
      var endpoint_@{id}_target = jsPlumb.addEndpoint('@GenerateId(id,true)', endpointValueTarget);
      var endpoint_@{id}_source = jsPlumb.addEndpoint('@GenerateId(id,true)', endpointSimilaritySource);

      @* Connect children *@
      jsPlumb.connect({
      source: endpoint_@{inputs.source.id}_source,
      target: endpoint_@{id}_target
      });
      jsPlumb.connect({
      source: endpoint_@{inputs.target.id}_source,
      target: endpoint_@{id}_target
      });
    }
    case TransformInput(id, transformer, inputs) => {
      @* Handle children *@
      @for(input <- inputs) {
        @connectOperator(input)
      }

      var endpoint_@{id}_target = jsPlumb.addEndpoint('@GenerateId(id,true)', endpointValueTarget);
      var endpoint_@{id}_source = jsPlumb.addEndpoint('@GenerateId(id,true)', endpointValueSource);

      @* Connect children *@
      @for(input <- inputs) {
        jsPlumb.connect({
        source: endpoint_@{input.id}_source,
        target: endpoint_@{id}_target
        });
      }
    }
    case PathInput(id, path) => {
      var endpoint_@{id}_source = jsPlumb.addEndpoint('@GenerateId(id,true)', endpointValueSource);
    }
  }
}